// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_TORQUE_LS_MESSAGE_H_
#define V8_TORQUE_LS_MESSAGE_H_

#include "src/base/logging.h"
#include "src/torque/ls/json.h"
#include "src/torque/ls/message-macros.h"

namespace v8 {
namespace internal {
    namespace torque {
        namespace ls {

            // Base class for Messages and Objects that are backed by either a
            // JsonValue or a reference to a JsonObject.
            // Helper methods are used by macros to implement typed accessors.
            class BaseJsonAccessor {
            public:
                template <class T>
                T GetObject(const std::string& property)
                {
                    return T(GetObjectProperty(property));
                }

                bool HasProperty(const std::string& property) const
                {
                    return object().count(property) > 0;
                }

                void SetNull(const std::string& property)
                {
                    object()[property] = JsonValue::JsonNull();
                }

                bool IsNull(const std::string& property) const
                {
                    return HasProperty(property) && object().at(property).tag == JsonValue::IS_NULL;
                }

            protected:
                virtual const JsonObject& object() const = 0;
                virtual JsonObject& object() = 0;

                JsonObject& GetObjectProperty(const std::string& property)
                {
                    if (!object()[property].IsObject()) {
                        object()[property] = JsonValue::From(JsonObject {});
                    }
                    return object()[property].ToObject();
                }

                JsonArray& GetArrayProperty(const std::string& property)
                {
                    if (!object()[property].IsArray()) {
                        object()[property] = JsonValue::From(JsonArray {});
                    }
                    return object()[property].ToArray();
                }

                JsonObject& AddObjectElementToArrayProperty(const std::string& property)
                {
                    JsonArray& array = GetArrayProperty(property);
                    array.push_back(JsonValue::From(JsonObject {}));

                    return array.back().ToObject();
                }
            };

            // Base class for Requests, Responses and Notifications.
            // In contrast to "BaseObject", a Message owns the backing JsonValue of the
            // whole object tree; i.e. value_ serves as root.
            class Message : public BaseJsonAccessor {
            public:
                Message()
                {
                    value_ = JsonValue::From(JsonObject {});
                    set_jsonrpc("2.0");
                }
                explicit Message(JsonValue& value)
                    : value_(std::move(value))
                {
                    CHECK(value_.tag == JsonValue::OBJECT);
                }

                JsonValue& GetJsonValue() { return value_; }

                JSON_STRING_ACCESSORS(jsonrpc)

            protected:
                const JsonObject& object() const { return value_.ToObject(); }
                JsonObject& object() { return value_.ToObject(); }

            private:
                JsonValue value_;
            };

            // Base class for complex type that might be part of a Message.
            // Instead of creating theses directly, use the accessors on the
            // root Message or a parent object.
            class NestedJsonAccessor : public BaseJsonAccessor {
            public:
                explicit NestedJsonAccessor(JsonObject& object)
                    : object_(object)
                {
                }

                const JsonObject& object() const { return object_; }
                JsonObject& object() { return object_; }

            private:
                JsonObject& object_;
            };

            class ResponseError : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_INT_ACCESSORS(code)
                JSON_STRING_ACCESSORS(message)
            };

            class InitializeParams : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_INT_ACCESSORS(processId)
                JSON_STRING_ACCESSORS(rootPath)
                JSON_STRING_ACCESSORS(rootUri)
                JSON_STRING_ACCESSORS(trace)
            };

            class FileListParams : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                // TODO(szuend): Implement read accessor for string
                //               arrays. "files" is managed directly.
            };

            class FileSystemWatcher : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_STRING_ACCESSORS(globPattern)
                JSON_INT_ACCESSORS(kind)

                enum WatchKind {
                    kCreate = 1,
                    kChange = 2,
                    kDelete = 4,

                    kAll = kCreate | kChange | kDelete,
                };
            };

            class DidChangeWatchedFilesRegistrationOptions : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_ARRAY_OBJECT_ACCESSORS(FileSystemWatcher, watchers)
            };

            class FileEvent : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_STRING_ACCESSORS(uri)
                JSON_INT_ACCESSORS(type)
            };

            class DidChangeWatchedFilesParams : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_ARRAY_OBJECT_ACCESSORS(FileEvent, changes)
            };

            class SaveOptions : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_BOOL_ACCESSORS(includeText)
            };

            class TextDocumentSyncOptions : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_BOOL_ACCESSORS(openClose)
                JSON_INT_ACCESSORS(change)
                JSON_BOOL_ACCESSORS(willSave)
                JSON_BOOL_ACCESSORS(willSaveWaitUntil)
                JSON_OBJECT_ACCESSORS(SaveOptions, save)
            };

            class ServerCapabilities : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_OBJECT_ACCESSORS(TextDocumentSyncOptions, textDocumentSync)
                JSON_BOOL_ACCESSORS(definitionProvider)
            };

            class InitializeResult : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_OBJECT_ACCESSORS(ServerCapabilities, capabilities)
            };

            class Registration : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_STRING_ACCESSORS(id)
                JSON_STRING_ACCESSORS(method)
                JSON_DYNAMIC_OBJECT_ACCESSORS(registerOptions)
            };

            class RegistrationParams : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_ARRAY_OBJECT_ACCESSORS(Registration, registrations)
            };

            class JsonPosition : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_INT_ACCESSORS(line)
                JSON_INT_ACCESSORS(character)
            };

            class Range : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_OBJECT_ACCESSORS(JsonPosition, start)
                JSON_OBJECT_ACCESSORS(JsonPosition, end)
            };

            class Location : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_STRING_ACCESSORS(uri)
                JSON_OBJECT_ACCESSORS(Range, range)
            };

            class TextDocumentIdentifier : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_STRING_ACCESSORS(uri)
            };

            class TextDocumentPositionParams : public NestedJsonAccessor {
            public:
                using NestedJsonAccessor::NestedJsonAccessor;

                JSON_OBJECT_ACCESSORS(TextDocumentIdentifier, textDocument)
                JSON_OBJECT_ACCESSORS(JsonPosition, position)
            };

            template <class T>
            class Request : public Message {
            public:
                explicit Request(JsonValue& value)
                    : Message(value)
                {
                }
                Request()
                    : Message()
                {
                }

                JSON_INT_ACCESSORS(id)
                JSON_STRING_ACCESSORS(method)
                JSON_OBJECT_ACCESSORS(T, params)
            };
            using InitializeRequest = Request<InitializeParams>;
            using RegistrationRequest = Request<RegistrationParams>;
            using TorqueFileListNotification = Request<FileListParams>;
            using GotoDefinitionRequest = Request<TextDocumentPositionParams>;
            using DidChangeWatchedFilesNotification = Request<DidChangeWatchedFilesParams>;

            template <class T>
            class Response : public Message {
            public:
                explicit Response(JsonValue& value)
                    : Message(value)
                {
                }
                Response()
                    : Message()
                {
                }

                JSON_INT_ACCESSORS(id)
                JSON_OBJECT_ACCESSORS(ResponseError, error)
                JSON_OBJECT_ACCESSORS(T, result)
            };
            using InitializeResponse = Response<InitializeResult>;
            using GotoDefinitionResponse = Response<Location>;

        } // namespace ls
    } // namespace torque
} // namespace internal
} // namespace v8

#endif // V8_TORQUE_LS_MESSAGE_H_
